//
// tinybpt.cpp -- Implementation files for package management tools, including functions for package installation, uninstallation, download and dependency management.
//

#include "tinybpt.h"
#include "json_utils.h"
#include "utils.h"
#include <iostream>
#include <fstream>
#include <vector>
#include <sys/stat.h>
#include <unistd.h>
#include <ctime>
#include <nlohmann/json.hpp>
#include <algorithm>
#include <cctype>
#include <limits.h>
#include <cstdlib>
#include <cerrno>
#include <cstring>
#include <httplib.h>
#include <iomanip>
#include <chrono>
#include <thread>
#include <sys/ioctl.h>

std::vector<Package> packages;
const std::string db_file = std::getenv("TINYBPT_DB_PATH") ? std::getenv("TINYBPT_DB_PATH") : "/etc/tinybpt/tinybpt_db.json";
std::string download_path = std::getenv("TINYBPT_DOWNLOAD_PATH") ? std::getenv("TINYBPT_DOWNLOAD_PATH") : "/var/cache/tinybpt";
std::string mirror_url;
Config config;

void read_mirror(const std::string &option) {
    try {
        config = read_config(db_file);
        mirror_url = config.mirror_url;
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return;
    }
}

void save_local_packages(const nlohmann::json &local_packages) {
    std::ifstream file(db_file);
    if (!file.is_open()) {
        std::cerr << "Failed to open file tinybpt_db.json" << std::endl;
        return;
    }
    nlohmann::json json_data;
    try {
        file >> json_data;
    } catch (const nlohmann::json::parse_error &e) {
        std::cerr << "Failed to parse JSON file: " << e.what() << std::endl;
        file.close();
        return;
    }
    file.close();
    json_data["localpackages"] = local_packages;
    std::ofstream out_file(db_file);
    if (!out_file.is_open()) {
        std::cerr << "Failed to open file tinybpt_db.json" << std::endl;
        return;
    }
    out_file << json_data.dump(4);
    out_file.close();
}

void remove_package(const std::string &name, bool force) {
    std::string package_path = download_path + "/" + name;
    std::string address_file_path = package_path + "/address";

    if (!force) {
        std::cout << "Do you want to read the address file? (y/n, default y): ";
        std::string read_choice;
        std::getline(std::cin, read_choice);

        if (read_choice.empty() || read_choice == "y" || read_choice == "Y") {
            std::ifstream address_file(address_file_path);
            if (!address_file.is_open()) {
                std::cerr << "Failed to open address file: " << address_file_path << std::endl;
                return;
            }

            std::string relative_path;
            while (std::getline(address_file, relative_path)) {
                std::cout << "Do you want to delete the file: " << relative_path << "? (y/n, default y): ";
                std::string choice;
                std::getline(std::cin, choice);
                if (choice.empty() || choice == "y" || choice == "Y") {
                    if (remove(relative_path.c_str()) == 0) {
                        std::cout << "Deleted file: " << relative_path << std::endl;
                    } else {
                        std::cerr << "Failed to delete file: " << relative_path << ", error: " << strerror(errno) << std::endl;
                    }
                }
            }
            address_file.close();
        }
    } else {
        std::ifstream address_file(address_file_path);
        if (!address_file.is_open()) {
            std::cerr << "Failed to open address file: " << address_file_path << std::endl;
            return;
        }

        std::string relative_path;
        while (std::getline(address_file, relative_path)) {
            if (remove(relative_path.c_str()) == 0) {
                std::cout << "Deleted file: " << relative_path << std::endl;
            } else {
                std::cerr << "Failed to delete file: " << relative_path << ", error: " << strerror(errno) << std::endl;
            }
        }
        address_file.close();
    }

    std::string command = "rm -rf " + package_path;
    if (system(command.c_str()) == 0) {
        std::cout << "Removed package directory: " << package_path << std::endl;
    } else {
        std::cerr << "Failed to remove package directory: " << package_path << std::endl;
    }
    std::ifstream file(db_file);
    if (!file.is_open()) {
        std::cerr << "Failed to open file tinybpt_db.json" << std::endl;
        return;
    }
    nlohmann::json json_data;
    try {
        file >> json_data;
    } catch (const nlohmann::json::parse_error &e) {
        std::cerr << "Failed to parse JSON file: " << e.what() << std::endl;
        file.close();
        return;
    }
    file.close();
    try {
        auto &packages_array = json_data["localpackages"];
        auto it = std::remove_if(packages_array.begin(), packages_array.end(), [&name](const nlohmann::json &pkg) { return pkg["name"] == name; });
        if (it == packages_array.end()) {
            std::cout << "Package info not found in localpackages: " << name << std::endl;
            return;
        }
        packages_array.erase(it, packages_array.end());
        save_local_packages(packages_array);
        std::cout << "Removed package info from localpackages: " << name << std::endl;
    } catch (const nlohmann::json::exception &e) {
        std::cerr << "Failed to update JSON data: " << e.what() << std::endl;
    }
}

bool create_symlinks(const std::string &output_path) {
    for (const auto &entry : std::filesystem::recursive_directory_iterator(output_path)) {
        if (entry.is_symlink()) {
            std::string link_path = entry.path().string();
            char target_path[PATH_MAX];
            ssize_t len = readlink(link_path.c_str(), target_path, sizeof(target_path) - 1);
            if (len == -1) {
                std::cerr << "Failed to read symlink: " << link_path << ", error: " << strerror(errno) << std::endl;
                return false;
            }
            target_path[len] = '\0';
            std::string new_link_path = "/" + entry.path().string().substr(output_path.length());
            std::filesystem::create_directories(std::filesystem::path(new_link_path).parent_path());
            if (std::filesystem::exists(new_link_path)) {
                std::filesystem::remove(new_link_path);
            }
            if (symlink(target_path, new_link_path.c_str()) != 0) {
                std::cerr << "Failed to create symlink: " << new_link_path << " -> " << target_path << ", error: " << strerror(errno) << std::endl;
                return false;
            } else {
                std::cout << "Created symlink: " << new_link_path << " -> " << target_path << std::endl;
            }
        }
    }
    return true;
}

bool process_extracted_files(const std::string &output_path, const std::string &name) {
    std::ofstream address_file(output_path + "/address");
    if (!address_file.is_open()) {
        std::cerr << "Failed to create address file" << std::endl;
        return false;
    }
    for (const auto &entry : std::filesystem::recursive_directory_iterator(output_path)) {
        if (entry.is_regular_file()) {
            std::string relative_path = entry.path().string().substr(output_path.length());
            std::string target_path = "/" + relative_path;
            if (entry.path().filename() == "address")
                continue;
            std::filesystem::create_directories(std::filesystem::path(target_path).parent_path());
            try {
                std::filesystem::copy(entry.path(), target_path, std::filesystem::copy_options::overwrite_existing);
                std::filesystem::remove(entry.path());
            } catch (const std::filesystem::filesystem_error &e) {
                std::cerr << "Failed to move file: " << entry.path() << " to " << target_path << ", error: " << e.what() << std::endl;
                return false;
            }
            address_file << "/" << relative_path << std::endl;
        }
    }
    address_file.close();
    if (!create_symlinks(output_path)) {
        std::cerr << "Download and installation failed for package: " << name << std::endl;
        return false;
    }
    for (const auto &entry : std::filesystem::directory_iterator(output_path)) {
        if (entry.is_regular_file() && entry.path().filename() != "address") {
            std::filesystem::remove(entry.path());
        } else if (entry.is_directory()) {
            std::filesystem::remove_all(entry.path());
        }
    }
    std::cout << "Installation complete: " << name << std::endl;
    return true;
}

void show_progress(const std::string &label, size_t current, size_t total) {
    const int default_bar_width = 50;
    const int label_width = 12;
    const int bar_width = default_bar_width;
    float progress = static_cast<float>(current) / total;
    int pos = static_cast<int>(bar_width * progress);

    std::cout << "\r\033[K";
    std::cout << "\033[1;32m" << std::left << std::setw(label_width) << label << " [";
    for (int i = 0; i < bar_width; ++i) {
        if (i < pos) std::cout << "\033[1;34m=\033[0m";
        else if (i == pos) std::cout << "\033[1;34m>\033[0m";
        else std::cout << " ";
    }
    std::cout << "\033[1;32m] " << int(progress * 100.0) << " %\033[0m";
    std::cout.flush();
}

bool download_package(const std::string &url, const std::string &output_path, const std::string &name) {
    std::cout << "Downloading package: " << name << std::endl;
    std::cout << "Output Path: " << output_path << std::endl;
    if (mkdir(output_path.c_str(), 0777) == -1 && errno != EEXIST) {
        std::cerr << "Failed to create directory: " << output_path << ", error: " << strerror(errno) << std::endl;
        return false;
    }
    std::string protocol = "://";
    size_t pos = mirror_url.find(protocol);
    if (pos != std::string::npos) {
        mirror_url = mirror_url.substr(pos + protocol.length());
    }
    std::string relative_url = url.substr(url.find(mirror_url) + mirror_url.length());
    httplib::SSLClient cli(mirror_url.c_str());
    cli.set_ca_cert_path("/etc/ssl/certs/cacert.pem");
    auto res = cli.Get(relative_url.c_str());
    if (!res) {
        std::cerr << "HTTP request failed for package: " << name << std::endl;
        std::cerr << "Error: " << httplib::to_string(res.error()) << std::endl;
        return false;
    }
    if (res->status != 200) {
        std::cerr << "Download failed: " << name << ", HTTP status: " << res->status << std::endl;
        std::cerr << "Response body: " << res->body << std::endl;
        return false;
    }
    std::string tar_file = output_path + "/" + url.substr(url.find_last_of('/') + 1);
    std::ofstream ofs(tar_file, std::ios::binary);
    size_t total_size = res->body.size();
    size_t written_size = 0;
    for (size_t i = 0; i < total_size; i += 1024) {
        size_t chunk_size = std::min(size_t(1024), total_size - i);
        ofs.write(&res->body[i], chunk_size);
        written_size += chunk_size;
        show_progress("Downloading: ", written_size, total_size);
    }
    ofs.close();
    std::cout << std::endl;
    if (system(("tar -xvf " + tar_file + " -C " + output_path).c_str()) != 0) {
        std::cerr << "Failed to extract package: " << name << std::endl;
        return false;
    }
    if (system(("rm " + tar_file).c_str()) != 0) {
        std::cerr << "Failed to remove tar file: " << tar_file << std::endl;
        return false;
    }
    return process_extracted_files(output_path, name);
}

void download_and_install_package(const std::string &name, const std::string &url) {
    std::string version = find_version_by_name(name, db_file);
    std::string package_url = url + version + ".tar.gz";
    std::string output_path = download_path + "/" + name + "/";
    bool download_success = download_package(package_url, output_path, name);
    if (!download_success) {
        std::cerr << "Download and installation failed for package: " << name << std::endl;
        return;
    }
    read_package_info(db_file, "packages");
    for (const auto &pkg : packages) {
        if (pkg.name == name) {
            update_local_package_info(db_file, pkg);
            break;
        }
    }
}

void install_package(const std::string &name, const std::string &url) {
    read_package_info(db_file, "localpackages");
    for (const auto &pkg : packages) {
        if (pkg.name != name)
            continue;
        std::string local_version = trim(pkg.version);
        std::string remote_version = trim(find_version_by_name(name, db_file));
        std::cout << "Local version: " << local_version << std::endl;
        std::cout << "Remote version: " << remote_version << std::endl;
        if (local_version == remote_version) {
            std::cout << "Package " << name << " is already installed, version matches" << std::endl;
            return;
        }
        std::cout << "Package " << name << " is already installed, but version does not match" << std::endl;
        std::cout << "Do you want to reinstall? (y/n): ";
        char choice;
        std::cin >> choice;
        if (choice == 'n' || choice == 'N') {
            return;
        }
    }
    std::cout << "Package " << name << " is not installed" << std::endl;
    read_package_info(db_file, "packages");
    for (const auto &pkg : packages) {
        if (pkg.name != name)
            continue;
        if (!pkg.dependencies.empty()) {
            std::vector<std::string> dependencies;
            std::string temp = pkg.dependencies;
            std::string::size_type pos = temp.find(",");
            while (pos != std::string::npos) {
                dependencies.push_back(temp.substr(0, pos));
                temp = temp.substr(pos + 1);
                pos = temp.find(",");
            }
            dependencies.push_back(temp);
            for (const auto &dep : dependencies) {
                std::string dep_name = find_package_by_info(dep, db_file);
                read_package_info(db_file, "localpackages");
                bool dep_installed = false;
                for (const auto &local_pkg : packages) {
                    if (local_pkg.name == dep_name) {
                        dep_installed = true;
                        break;
                    }
                }
                if (!dep_installed) {
                    install_package(dep_name, url);
                }
            }
        }
        download_and_install_package(name, url);
        return;
    }
    std::cout << "Package not found: " << name << std::endl;
}

inline void from_json(const nlohmann::json& j, Package& p) {
    j.at("name").get_to(p.name);
    j.at("version").get_to(p.version);
    j.at("info").get_to(p.info);
    j.at("download_time").get_to(p.download_time);
    j.at("dependencies").get_to(p.dependencies);
}